#include <stdio.h>
#include <stdlib.h>
#define NETMAP_WITH_LIBS
#include <net/netmap_user.h>
#include <poll.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "net.h"     /*   自定义头文件一定要放最下面啊， #pragma once  #pragma pack(1) 容易坑到你 */
#define TYPE_UDP 0x1

/**
 * @brief udp header
 *  length: 4 + 4 = 8Bytes
 */
#define MAX_PKT_UDP     (0xffff)
#define MAX_DATA_UDP    (0xffec)

struct udp_hdr {
    uint16_t s_port;    // if not used, set zero
    uint16_t d_port;

    uint16_t length;    // including this header and the data
    uint16_t check;     // check sum
}__attribute__((packed));

/**
 * @brief udp package
 */
struct udp_pkt {
    struct eth_hdr eth_hdr;
    struct ip_hdr ip_hdr;
    struct udp_hdr udp_hdr;
    uint8_t data[0];
}__attribute__((packed));

uint8_t g_proto, g_ttl, g_tos;
uint8_t g_src_ip[IPV4_LEN], g_dst_ip[IPV4_LEN];
uint8_t g_src_mac[MAC_LEN], g_dst_mac[MAC_LEN];
uint16_t g_src_port= 0, g_dst_port= 0, g_ip_flags = 0, g_times = 0;
/************************************
 * @brief global interface
 ************************************/
void global_init(void)
{
    g_tos = 0x00;
    if(!g_src_port)
        g_src_port = 90;
    if(!g_ip_flags)
        g_ip_flags = 0x40;
    if(!g_times)
        g_times = 10;
}

void set_src_port(uint16_t port)
{
    g_src_port = port;
}
/************************************
 * @brief 解析配置文件
 ************************************/
uint8_t C2I(char ch)
{
    uint8_t tmp;
    switch(ch){
        case 'A':
        case 'a':tmp = 10;
        break;
        case 'B':
        case 'b':tmp = 11;
        break;
        case 'C':
        case 'c':tmp = 12;
        break;
        case 'D':
        case 'd':tmp = 13;
        break;
        case 'E':
        case 'e':tmp = 14;
        break;
        case 'F':
        case 'f':tmp = 10;
        break;
        default:tmp = ch - '0';
        break;
    }
    return tmp;
}
uint8_t A2H(char **str)
{
    char *p = *str;
    uint16_t len = 0, power = 16;
    uint16_t ret = 0, tmp;
    while(*p != ':' && *p != '.' && *p != '\0' && *p != 0x0a){
        len++;
        p++;
    }
    if(len > 5){
        printf("A2I strlen error\n");
        return;
    }
    if(*p != '\0')
        *str = p+1;
    --p;                    // p指向个位

    ret = C2I(*p);     
    --p;                    // p指向十位
    --len;
    while (len--)
    {
        ret += C2I(*p) * power;
#if DEBUG_NET
        printf("ch - %c: hex - %x\n", *p, ret);
#endif
        power *= 16;
        --p;
    }
    return ret;
}

uint16_t A2I(char **str)
{
    char *p = *str;
    uint16_t len = 0, power = 10;
    uint16_t ret = 0;
    while(*p != ':' && *p != '.' && *p != '\0' && *p != 0x0a){
        len++;
        p++;
    }
    if(len > 5){
        printf("A2I strlen error\n");
        return;
    }
    if(*p != '\0')
        *str = p+1;
    --p;                    // p指向个位
    ret = (*p) - '0';     
    --p;                    // p指向十位
    --len;
    while (len--)
    {
        ret += (*p - '0') * power;
        power *= 10;
        --p;
    }
    return ret;
}

void parse_line(char *buf)
{
    char* tmp = buf;
    uint32_t ip_bytes = IP_LEN_V4;
    uint32_t loop = 0;
    buf+=4;
    switch (*tmp)
    {
    case 't':
        if(tmp[2] == 's')
            g_times = A2I(&buf);
        if(tmp[2] == 'l')
            g_ttl = A2I(&buf);
        
        break;

    case 's':
        while (loop < IP_LEN_V4) g_src_ip[loop++] = A2I(&buf);
        // g_src_port = A2I(&buf);
        break;

    case 'd':
        while (loop < IP_LEN_V4) g_dst_ip[loop++] = A2I(&buf);
        g_dst_port = A2I(&buf);
        break;

    case 'p':
        break;

    case 'm':
        if(tmp[2] == 's') {
            while (loop < MAC_LEN) g_src_mac[loop++] = A2H(&buf);
            break;
        }
        if(tmp[2] == 'd') {
            while (loop < MAC_LEN) g_dst_mac[loop++] = A2H(&buf);
            break;
        }
        break;
    default:
        break;
    }
}

void load_config(char *path)
{
    char buf[1024] = {0};
    FILE *fp = fopen(path, "r");
    if(fp == NULL){
        printf("Path error: %s\n", path);
        return;
    }

    while (fgets(buf, 1024, fp))
    {
        if(buf[0] == '#')
            continue;
        parse_line(buf);
        memset(buf, 0, 1024);
    }
    global_init();
}
/************************************
 * @brief 传输 层操作包
 ************************************/
/**
 * @brief _udp_checksum
 * 
 * @param raw_pkt 
 * @return uint16_t 
 */
uint16_t _udp_checksum(void *raw_pkt)
{
    uint16_t check_sum = 0, *p;
    struct ip_hdr *ip_h = IP_HDR(raw_pkt);
    struct udp_hdr *udp_h = UDP_HDR(ip_h);

    /*   udp的checksum 只计算伪头部校验和
     *   src_ip : dst_ip : 0x1100 (17) : udp_length
     */
    p = (uint16_t*)&ip_h->ip_src[0];
    while(p < &ip_h->ip_dst[IP_LEN_V4-1]) check_sum += *p++;
    check_sum += 0x1100;
    check_sum += udp_h->length;
    check_sum = (check_sum & 0xffff) + (check_sum >> 16);
    return check_sum;
}
void set_udpchksum(void *pkt)
{
    struct ip_hdr *ip_h = IP_HDR(pkt);
    struct udp_hdr *udp_h = UDP_HDR(ip_h);
    udp_h->check = 0;
    udp_h->check = _udp_checksum(pkt);
}
uint16_t get_udpchksum(void *pkt)
{
    uint16_t chk, ret;
    struct ip_hdr *ip_h = IP_HDR(pkt);
    struct udp_hdr *udp_h = UDP_HDR(ip_h);
    chk = udp_h->check;
    udp_h->check = 0;
    ret = _udp_checksum(pkt);
    udp_h->check = chk;
    return ret;
}
/**
 * @brief create_udphdr
 */
void create_udphdr(void *pkt, uint16_t data_len)
{
    struct ip_hdr *ip_h = IP_HDR(pkt);
    struct udp_hdr *udp_h = UDP_HDR(ip_h);
    udp_h->d_port = htons(g_dst_port);
    udp_h->s_port = htons(g_src_port);
    udp_h->length = htons(data_len);
    set_udpchksum(pkt);
}
/************************************
 * @brief IP 层操作包
 ************************************/
uint16_t _ipv4_checksum(void *raw_pkt)
{
    uint32_t check_sum = 0;
    struct ip_hdr * p_hdr = (char*)raw_pkt + sizeof(struct eth_hdr);
    uint16_t *p = p_hdr,
    loop_len = p_hdr->hdr_len * WORD_SIZE/2;

    while (loop_len--) check_sum += *p++;

    check_sum = (check_sum & 0xffff) + (check_sum >> 16);

    return ~((uint16_t)check_sum);
}
/**
 * @brief set_ipv4chksum
 * 发送时设置 check_sum
 */
void set_ipv4chksum(void *pkt)
{
    struct ip_hdr * p_hdr = (char*)pkt + sizeof(struct eth_hdr);
    p_hdr->check = 0;
    p_hdr->check = _ipv4_checksum(pkt);
}
/**
 * @brief get_chksum
 * 接收时计算 check_sum
 */
uint16_t get_ipv4chksum(void *pkt)
{
    uint16_t chk, ret;
    struct ip_hdr * p_hdr = (char*)pkt + sizeof(struct eth_hdr);
    chk = p_hdr->check;
    p_hdr->check = 0;
    ret = _ipv4_checksum(pkt);
    p_hdr->check = chk;
    return ret;
}
/**
 * @brief create ip header
 * 
 */
u_int16_t alloc_id(void)
{
    static ip_id = 0;
    return ++ip_id ?ip_id:1;
}

void _init_iphdr(struct ip_hdr *i_hdr)
{
    char loop = 0;
    i_hdr->flags = g_ip_flags;
    i_hdr->ttl = g_ttl;
    i_hdr->tos = g_tos;
    i_hdr->version = IP_VERSION;
    while (loop < IPV4_LEN) i_hdr->ip_src[loop++] = g_src_ip[loop];
    while (loop >= 0) i_hdr->ip_dst[loop--] = g_dst_ip[loop];
}
void  create_iphdr(void *data, uint8_t hdr_len, uint8_t proto, uint16_t total_len)
{
    struct ip_hdr *i_hdr = IP_HDR(data);
    i_hdr->id = htons(alloc_id());
    i_hdr->hdr_len = hdr_len;
    i_hdr->proto = proto;
    i_hdr->total_len = htons(total_len);
    _init_iphdr(i_hdr);
    set_ipv4chksum(data);
}
/************************************
 * @brief ether 层操作包
 ************************************/
void  create_ethhdr(void *data, uint16_t type)
{
    struct eth_hdr *p_hdr = data;
    strncpy(p_hdr->src, g_src_mac, MAC_LEN);
    strncpy(p_hdr->dst, g_dst_mac, MAC_LEN);
    p_hdr->type = htons(type);
}
/************************************
 * @brief raw_pkt 操作
 ************************************/
void print_raw(void *pkt)
{
    struct ip_hdr *i_hdr = IP_HDR(pkt);
    uint32_t i = 1, loop = ntohs(i_hdr->total_len) + sizeof(struct eth_hdr);
    uint8_t *p = pkt;
#if DEBUG_NET
    printf("------------- raw_data --------------\n");
#endif
    while (i <= loop)
    {
        printf("%02x ", *p);
        if(i%16 == 0)
            putchar('\n');
        else if(i%8==0)
            putchar('\t');
        ++p;
        ++i;
    }
    putchar('\n');
}

struct udp_pkt *  create_udppkt(void *data)
{
    uint16_t data_len = strlen((char*)data);
    uint16_t pkt_len = data_len + sizeof(struct udp_pkt);
    struct udp_pkt *udp_pkt = malloc(pkt_len);

    create_ethhdr(udp_pkt, PROTO_IP);
    create_iphdr(udp_pkt, 5, PROTO_UDP, pkt_len - sizeof(struct eth_hdr));
    create_udphdr(udp_pkt, sizeof(struct udp_hdr) + data_len);
    memcpy(udp_pkt->data, data, data_len);
    return udp_pkt;
}
/************************************
 * @brief input 操作
 ************************************/
void main_msg()
{
    printf("==============================\n");
    printf("1. Change port.\n");
    printf("==============================\n");
    printf("*cmd:\n");
}
void port_msg()
{
    printf("==============================\n");
    printf("port:\n");
}
void input_subsys(void)
{
    uint16_t port;
    uint8_t cmd;
    main_msg();
    scanf("%ud", &g_src_port);
    // scanf("%ud", &port);
    // switch (cmd)
    // {
    // case 1:
    //     port_msg();
    //     set_src_port(port);
    //     break;
    // default:
    //     break;
    // }
}
/**
 * @brief network map to memory
 *  raw socket  
 *  pf_ring
 *  netmap
 *  dpdk
 */
void model_test(void);

int main(int argc, char* argv[])
{
    /**************
     * 测试代码
     **************/
    // model_test();

    /***************
     * 主函数代码 
     ***************/
    char data[DATA_BUF_SIZE] = {0};
    int ret;
    uint8_t *raw_data;
    uint16_t s_port, pkt_len;
    struct pollfd fds;
    struct nm_desc *nmr = nm_open("netmap:eno1", NULL, 0, 0);
    if(nmr == NULL) return -1;

    bzero(&fds, sizeof(fds));
    fds.fd = nmr->fd;
    fds.events = POLLOUT;
    
    scanf("%ud", &s_port);
    scanf("%s", data);
    set_src_port((uint16_t)s_port);

    pkt_len = sizeof(struct udp_pkt) + strlen(data);
    load_config(CONFIG_FILE);

    int count=0;
    for(;;){
        if(fds.events == POLLIN){
            // nm_dispatch(nmr, -1, my_cb, (void *)&count);
        }else {
            raw_data = create_udppkt(data);
            if(nm_inject(nmr, raw_data, pkt_len) > 0){
                count++;
            }
        }
        poll(&fds, 1, 1000);
        if(count >= g_times)
            break;
    }
    print_raw(raw_data);
    printf("pkt count: %d\n", count);
    nm_close(nmr);
}

/************************************
 * @brief 测试代码
 ************************************/
void model_test(void)
{
    /*   0 header size   */
    printf("ether header size:%3d\nip header size:%3d\nudp header size:%3d\nudp paket size:%d\n" \
                        ,sizeof(struct eth_hdr)
                        ,sizeof(struct ip_hdr)
                        ,sizeof(struct udp_hdr)
                        ,sizeof(struct udp_pkt));
    uint32_t raw_data[11] = {  
                            0x00000000,
                            0x00000000,
                            0x00000000,
                            0x00450008,
                            0x3fc71e00,
                            0x11400040,
                            0x007f0000,
                            0x007f0100,
                            0xe7a60100,
                            0x0a005000,
                            0x0a730000,
                            };

    print_raw(raw_data);
    printf("udp-chksum:%x\tip-chksum:%x\n", get_udpchksum(raw_data), get_ipv4chksum(raw_data));
    load_config(CONFIG_FILE);

    uint8_t data[9] = {'a', 'a', 'a', 'a', 'a', 'a', 'a'};
    data[7] = 0x0a;
    data[8] = '\0';
    // input_subsys();
    // print_raw(create_udppkt(data));
    printf("------------- test completed --------------\n");
}